# -*- coding: utf-8 -*-


# cmap2owl -- Helper application to convert from concept maps to OWL ontologies
# Copyright (c) 2008-2013  Rodrigo Rizzi Starr
#  
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#  
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#  
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.


'''
A generic XML parser module.

This module is based on a state machine

@author: Rodrigo Rizzi Starr
@copyright: Copyright © 2008-2013 Rodrigo Rizzi Starr
@license: MIT License
@contact: rodrigo.starr@gmail.com
'''


from xml.sax import saxutils
from xml.sax import make_parser
from xml.sax.handler import feature_namespaces

class XMLParser(saxutils.handler.ContentHandler):
   """
   A parser class. This should be subclassed by any class that intend to parse a
    XML file.
   
   This class works by using StateHandlers to handle what to do in each state.
   It will call at the correct times each state transition. It will also change
   the state according to what is defined by the current StateHandler.
   
   The StateHandlers are stacked in a list and "unstacked" when necessary.
   
   """
   
   def __init__(self, fileName, baseState):
      
      self.fileName  = fileName
      self.baseState = baseState
      
      # The state stack (the baseState is aways the first element) and active handler
      self.stack = [baseState]
      self.activeHandler = baseState
      
      # The parser
      self.parser = make_parser()
      # Disable namespace processing
      self.parser.setFeature(feature_namespaces, False)
      # Set self as the handler, so it can delegate to the StateHandlers
      self.parser.setContentHandler(self)
      
   def getData(self):
      "Return the data created during the parsing."
      return self.baseState.getData()
      
   def parse(self):
      """Parses filename."""
      self.parser.parse(open(self.fileName))
      
   def startElement(self, name, attrs):
      """
         Called when a new tag is found. Finds the next handler and pushes it into the stack. Then sends the tag and attributes to the next handler, and sends the ChangeState event to the previous class.
      """
      
      self.activeHandler.ChangeState(name)  # Tells the handler that we are going to move on
      
      # Change the state that is processing the data. activeHandler changes here
      self.PushHandler(self.activeHandler.GetNextState(name))
      
      self.activeHandler.EnterState()  # Now enters in the new state.
      self.activeHandler.ConsumeAttrs(attrs)
         
   def characters(self, data):
      """Collects the data so it can be processed in only one step by the handler"""
      self.activeHandler.ConsumeChar(data)
      
   def endElement(self, name):
      """
         Called when the closing element of a tag is found. Pops the current handler from the stack and return the control to the previous one.
      """
      
      self.activeHandler.LeaveState()
      self.PopHandler()
      self.activeHandler.InsideTagClosed(name)
      
      
   def PushHandler(self, handler):
      """
         Sets the class that is processing the elements
      """
      self.stack.append(handler)
      self.activeHandler = handler
      
   def PopHandler(self):
      self.stack.pop()
      self.activeHandler = self.stack[-1]
      
   
   
class StateHandler:
   """
   This class implements a state of the state machine used to parse a XML file.
   This class is aways associated with a certain transition, that is triggered by a tag in the XML file. So, the tag is what is used for the transition. Also, this class contains a dictionary relating each tag that it may encounter with the StateHandler that should deal with that tag.
   
   A state handler must define this interface to process incoming XML info:
   
   EnterState(): called when this state has just been started. Doesn't receive anything and is there because some initialization may be necessary.
   
   ConsumeAttrs(attrs): process the attributes related to the tag. It is called just after EnterState
   
   ConsumeChar(string): called when the SAX parser generates  a "characters" event. As such, nothing should be assumed as for the size of string.
   
   GetNextState(tag): called when the parser finds a new element. Should return the handler for the "tag".
   
   ChangeState(tag): called when a new tag is found by the parser. Should be used to do any clean up necessary before passing control for the next StateHandler. "tag" contains the name of the tag found, so it is possible to take specific action depending on the tag.
   
   InsideTagClosed(tag): called when an element that was inside the one processed by this state is closed and the control is returning to the current handler. "tag" is the name of the closed tag.
   
   LeaveState(): called when the closing tag related to this state is found. Should clean up the info.
   
   Besides this interface, the attribute stateTransitions of this class can also be accessed. It contains a dictionary relating tag names with StateHandlers. The preferred method of setting this up is by passing the dictionary to the constructor. The other parameter for the constructor is a "data" variable, that can contain any structure over which the StateHandler will work.
   """
   
   def __init__(self, stateTransitions, data = None):
      "Initializes the state transitions of the handler"
      
      self.stateTransitions = stateTransitions
      self.data = data
      
   def getData(self):
      "Returns the data element (data will be modified after parsing)"
      return self.data
   
   def GetNextState(self, tag):
      return self.stateTransitions[tag]
   
   def EnterState(self):
      pass
   
   def ConsumeAttrs(self, attrs):
      pass

   def ConsumeChar(self, string):
      pass
      
   def ChangeState(self, tag):
      pass
   
   def InsideTagClosed(self, tag):
      pass

   def LeaveState(self):
      pass
   
   
class DummyStateHandler(StateHandler):
   "A handler that doesn't do anything and accepts everything."
   
   def GetNextState(self, tag):
      try:
         return self.stateTransitions[tag]
      except KeyError:
         return self
   
class LoggingStateHandler(StateHandler):
   "A handler that only prints what is happening."
   
   def __init__(self, data = 0):
      "Initializes the state transitions of the handler. Data is the indenting level."
      
      StateHandler.__init__(self, {}, data)
      
   def PrintIndent(self, text):
      "Prints text with the correct indentation"
      print self.data*" " + text
      
   def GetNextState(self, tag):
      return LoggingStateHandler(self.data + 1)
   
   def EnterState(self):
      self.PrintIndent("Entering state")
   
   def ConsumeAttrs(self, attrs):
      self.PrintIndent("Got attributes: %s" % attrs.keys())

   def ConsumeChar(self, string):
      self.PrintIndent("Got chars: %s" % string)
      
   def ChangeState(self, tag):
      self.PrintIndent("Changing to state: %s" % tag)
   
   def InsideTagClosed(self, tag):
      self.PrintIndent("Coming back from state: %s" % tag)

   def LeaveState(self):
      self.PrintIndent("Closing myself")
   
if __name__ == "__main__":
   p = XMLParser("test/Mapa simples.cxl", LoggingStateHandler(1))
   
   p.parse()
   
